.. _глава_75_start:

Объединения
============

.. index::
    объединения

Объединения, низкоуровневая функция, унаследованная от языка программирования C, позволяют
использовать одну и ту же область памяти для нескольких членов.
Объединения очень похожи на структуры со следующими основными отличиями:
∙ Объединения определяются ключевым словом union.
∙ Члены объединения не являются независимыми; они делят одну и ту же область памяти.
область памяти.
Как и структуры, объединения могут иметь функции-члены.
Приведенные ниже примеры будут иметь разные результаты в зависимости от того, где они
компилируются в 32-битной или 64-битной среде. Чтобы избежать путаницы
результатов, используйте ключ компилятора -m32 при компиляции примеров в этой главе.
главе. В противном случае ваши результаты могут отличаться от моих из-за выравнивания,
которое мы рассмотрим в одной из следующих глав.
Естественно, объекты struct настолько велики, насколько это необходимо для размещения всех их
членов:

.. code-block:: d

    // Note: Please compile with the -m32 compiler switch
    struct S
    {
        int i;
        double d;
    }
    // ...
    writeln(S.sizeof);


Поскольку int имеет длину 4 байта, а double - 8 байт, размер этой структуры равен
сумма их размеров:

.. code-block:: d

    12

В отличие от этого, размер объединения с одинаковыми членами равен только его
самый большой член:

.. code-block:: d

    union U
    {
        int i;
        double d;
    }
    // ...
    writeln(U.sizeof);


4-байтовый int и 8-байтовый double делят одну и ту же область. В результате размер
всего объединения равен его самому большому члену:

.. code-block:: d

    8

Объединения не являются средством экономии памяти. Невозможно поместить несколько данных в
одну и ту же ячейку памяти. Цель объединения - использовать одну и ту же область для
разных типов данных в разное время. Только один из членов может быть использован
надежно использоваться в одно и то же время. Однако, несмотря на то, что это может быть не переносимо на разные
платформах, члены объединения могут использоваться для доступа к фрагментам других
членов.
В одном из примеров ниже используется преимущество typeid для запрета доступа к
членам, отличным от того, который действует в данный момент:

На следующей диаграмме показано, как 8 байт вышеуказанного объединения разделяются между
его членами:

Insertar la diagrama

Либо все 8 байт используются для члена double, либо только первые 4 байта
используются для члена int, а остальные 4 байта не используются.
Объединения могут иметь столько членов, сколько необходимо. Все члены будут совместно использовать
одну и ту же область памяти.
Тот факт, что для всех членов используется одна и та же область памяти, может иметь
удивительные последствия. Например, давайте инициализируем объект союза его членом int
а затем обратимся к его члену double:

.. code-block:: d

    auto u = U(42); // initializing the int member
    writeln(u.d); // accessing the double member

Инициализация члена int значением 42 устанавливает только первые 4 байта, и это
непредсказуемым образом влияет на член double:

.. code-block:: d

    2.07508e-322

В зависимости от эндианальности микропроцессора, эти 4 байта могут быть расположены
в памяти как 0|0|0|42, 42|0|0|0, или в каком-то другом порядке. По этой причине значение
двойного члена может выглядеть по-разному на разных платформах.

.. index::
    анонимные объединения

Анонимные объединения
----------------------

Анонимные объединения указывают, какие члены определенного пользователем типа имеют общую
область:

.. code-block:: d

    struct S
    {
        int first;
        union
        {
            int second;
            int third;
        }
    }
    // ...
    writeln(S.sizeof);

Последние два члена S делят одну и ту же область. Таким образом, размер структуры представляет собой
всего два целых числа: 4 байта, необходимые для первого, и еще 4 байта, которые делят между собой
вторым и третьим:

.. code-block:: d

    8

Разбиение других членов
-----------------------

Объединения можно использовать для доступа к отдельным байтам переменных других типов. Например,
Например, с их помощью легко получить доступ к 4 байтам IPv4-адреса по отдельности.
32-битное значение адреса IPv4 и массив фиксированной длины могут быть определены как
два члена объединения:

.. code-block:: d

    union IpAddress {
    uint value;
    ubyte[4] bytes;
    }

Члены этого объединения будут делить одну и ту же область памяти, как показано на следующем рисунке
следующем рисунке:

Insertar diagrama

Например, когда объект этого объединения инициализируется значением 0xc0a80102 (значение
соответствующее точечной форме 192.168.1.2), элементы массива bytes
автоматически будут иметь значения четырех октетов:

.. code-block:: d

    import std.stdio;

    void main()
    {
        auto address = IpAddress(0xc0a80102);
        writeln(address.bytes);
    }

При выполнении в little-endian системе октеты будут отображаться в обратном порядке по сравнению с их
точечной формы:
[2, 1, 168, 192]
Обратный порядок октетов - еще один пример того, как обращение к разным
членам объединения может привести к непредсказуемым результатам. Это происходит потому, что
поведение объединения гарантируется только в том случае, если это объединение используется только через один из его членов
его членов. Нет никаких гарантий относительно значений других членов, кроме
тех, которыми инициализирован союз.
Хотя эта глава не имеет прямого отношения к данному разделу, bswap из модуля core.bitop
полезен при решении проблем с конечностью. bswap возвращает свой параметр
после обмена байтами. Также, пользуясь преимуществом значения endian из модуля
модуля std.system, октеты предыдущего IPv4-адреса могут быть выведены в ожидаемом порядке
ожидаемом порядке после замены байтов:

.. code-block:: d

    import std.system;
    import core.bitop;
    // ...
    if (endian == Endian.littleEndian)
    {
        address.value = bswap(address.value);
    }

Вывод:

.. code-block:: d

    [192, 168, 1, 2]

Тип IpAddress рассматривается в качестве простого примера; в целом, лучше
для нетривиальных программ лучше предусмотреть специальный сетевой модуль.

Примеры
-------

**Протокол связи**

В некоторых протоколах, таких как TCP/IP, значения определенных частей пакета протокола
зависят от конкретного значения внутри того же пакета. Обычно это поле в
заголовке пакета, которое определяет значения последующих байтов. Объединения
могут быть использованы для представления таких протокольных пакетов.

Следующая структура представляет собой пакет протокола, который имеет два вида:

.. code-block:: d

    struct Host
    {
        // ...
    }

    struct ProtocolA
    {
        // ...
    }

    struct ProtocolB
    {
        // ...
    }

    enum ProtocolType
    {
        A,
        B
    }

    struct NetworkPacket
    {
        Host source;
        Host destination;
        ProtocolType type;
        union
        {
            ProtocolA aParts;
            ProtocolB bParts;
        }

        ubyte[] payload;
    }


В приведенной выше структуре можно использовать член type для определения того, какой тип
aParts или bParts объединения, которое будет использоваться.

**Дискриминантное объединение**

Дискриминантное объединение - это структура данных, которая обеспечивает безопасность типов по сравнению с обычным объединением
объединением. В отличие от объединения, она не позволяет обращаться к членам, отличным от
одного, который действует в данный момент.
Ниже приведен простой тип дискриминантного объединения, который поддерживает только два
типов: int и double. В дополнение к объединению для хранения данных он содержит
TypeInfo (:ref:`Object<глава_56_start>`), чтобы знать, какой из двух членов союза является действительным.
действительным.

.. code-block::


    import std.stdio;
    import std.exception;

    struct Discriminated
    {
    private:
        TypeInfo validType_;
        union
        {
            int i_;
            double d_;
        }

    public:
        this(int value)
        {
            // This is a call to the property function below:
            i = value;
        }
        // Setter for 'int' data
        void i(int value)
        {
            i_ = value;
            validType_ = typeid(int);
        }
        // Getter for 'int' data
        int i() const
        {
            enforce(validType_ == typeid(int),
                "The data is not an 'int'.");
            return i_;
        }

        this(double value)
        {
            // This is a call to the property function below:
            d = value;
        }
        // Setter for 'double' data
        void d(double value)
        {
            d_ = value;
            validType_ = typeid(double);
        }
        // Getter for 'double' data
        double d() const
        {
            enforce(validType_ == typeid(double),
                "The data is not a 'double'.");
            return d_;
        }
        // Identifies the type of the valid data
        const(TypeInfo) type() const
        {
            return validType_;
        }
    }

    unittest
    {
        // Let's start with 'int' data
        auto var = Discriminated(42);
        // The type should be reported as 'int'
        assert(var.type == typeid(int));
        // 'int' getter should work
        assert(var.i == 42);
        // 'double' getter should fail
        assertThrown(var.d);
        // Let's replace 'int' with 'double' data
        var.d = 1.5;
        // The type should be reported as 'double'
        assert(var.type == typeid(double));
        // Now 'double' getter should work ...
        assert(var.d == 1.5);
        // ... and 'int' getter should fail
        assertThrown(var.i);
    }

Это всего лишь пример. Вам следует подумать об использовании алгебраических и вариативных выражений из
модуля std.variant в своих программах. Кроме того, этот код может использовать
воспользоваться другими возможностями D, такими как шаблоны (:ref:`Шаблоны<глава_64_start>`) и миксины (:ref:`Примеси<глава_80_start>`).
для уменьшения дублирования кода.
Независимо от того, какие данные хранятся, существует только один тип Discriminated
тип. (Альтернативное шаблонное решение может принимать тип данных в качестве параметра шаблона
в качестве параметра шаблона, и тогда каждый инстанс шаблона будет представлять собой отдельный
тип). По этой причине можно иметь массив объектов Discriminated,
что позволяет создать коллекцию, элементы которой могут быть разных типов.
Однако пользователь все равно должен знать действительный член массива, прежде чем обратиться к нему. Для примера
Например, следующая функция определяет тип действительных данных с помощью свойства
свойством type объекта Discriminated:

.. code-block:: d

    void main()
    {
        Discriminated[] arr = [Discriminated(1),
            Discriminated(2.5)];
        foreach (value; arr)
        {
            if (value.type == typeid(int))
            {
                writeln("Working with an 'int'  : ", value.i);
            }
            else if (value.type == typeid(double))
            {
                writeln("Working with a 'double': ", value.d);
            }
            else
            {
                assert(0);
            }
        }
    }

.. code-block:: console

    Working with an 'int'  : 1
    Working with a 'double': 2.5



